<?php

/*
This is library file to payment modules. Cant be reused to 
communicate with banks.

Copyright (c) 2010 Ingvar Harjaks <ingvarharjaks@gmail.com>
Copyright (c) 2011 Leone Viru <leone.viru@gmail.com>
All costs of this development were covered by open source fiendy
company BrightSide OÜ <www.brightside.ee>

Pizza is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Pizza is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Pizza. If not, see <http://www.gnu.org/licenses/>.
*/

class Pizza extends Controller
{
    public $bank;
    public $service_id; /* Unique service identifier */
    public $vk_version;  /* I-pizza framework ver */
    public $errors = array();
    public $additional_data = array();



    public function index()
    {
        $data = array();
        $this->language->load('common/pizza');
        $this->language->load('payment/' . $this->bank);
        $this->id = 'payment';

        $data = array_merge($data, $this->getBankPaymentDetails());

        $mac = $this->make_mac($data);
        $signed_mac = $this->sign_mac($mac);

        $data["VK_MAC"] = $signed_mac;
        $data['VK_RETURN'] = $this->url->link("payment/{$this->bank}/callback", '', 'SSL');
        $data["VK_LANG"] = $this->get_language();
        $data = array_merge($data, $this->additional_data);

        $data['action'] = $this->config->get("{$this->bank}_bank_url");
        $data['back'] = $this->url->link("checkout/{$this->bank}", '', 'SSL');
        $data['button_confirm'] = $this->language->get('button_confirm');
        $data['button_back'] = $this->language->get('button_back');
        $data['bank'] = $this->bank;

        if ($this->hasError()) {
            return $this->loadView(array_merge($data, $this->errors), '/template/common/pizza_failture.tpl');
        }
        return $this->loadView($data, '/template/common/pizza.tpl');
    }


    /* Service fileds in right order for service you need */

    protected  function get_service_fields()
    {
        $services = array();
        $services["1001"] = array(
            "VK_SERVICE",
            "VK_VERSION",
            "VK_SND_ID",
            "VK_STAMP",
            "VK_AMOUNT",
            "VK_CURR",
            "VK_ACC",
            "VK_NAME",
            "VK_REF",
            "VK_MSG"
        );
        $services["1002"] = array(
            "VK_SERVICE",
            "VK_VERSION",
            "VK_SND_ID",
            "VK_STAMP",
            "VK_AMOUNT",
            "VK_CURR",
            "VK_REF",
            "VK_MSG"
        );
        $services["1101"] = array(
            "VK_SERVICE",
            "VK_VERSION",
            "VK_SND_ID",
            "VK_REC_ID",
            "VK_STAMP",
            "VK_T_NO",
            "VK_AMOUNT",
            "VK_CURR",
            "VK_REC_ACC",
            "VK_REC_NAME",
            "VK_SND_ACC",
            "VK_SND_NAME",
            "VK_REF",
            "VK_MSG",
            "VK_T_DATE"
        );
        $services["1901"] = array(
            "VK_SERVICE",
            "VK_VERSION",
            "VK_SND_ID",
            "VK_REC_ID",
            "VK_STAMP",
            "VK_REF",
            "VK_MSG"
        );
        $services["1902"] = array(
            "VK_SERVICE",
            "VK_VERSION",
            "VK_SND_ID",
            "VK_REC_ID",
            "VK_STAMP",
            "VK_REF",
            "VK_MSG",
            "VK_ERROR_CODE"
        );

        if (array_key_exists($this->service_id, $services)) {
            return $services[$this->service_id];
        } else {
            $this->addError(sprintf("No such service: %s!", $this->service_id));
            return array();
        }
    }

    /* Returns all data of currently processed order */

    private function get_order_info($order_id = false)
    {
        if ($order_id == false) {
            $order_id = $this->session->data['order_id'];
        }
        $this->load->model('checkout/order');
        $order_info = $this->model_checkout_order->getOrder($order_id);
        return $order_info;
    }
    /* Collect all required data to be forwarded to bank and put it
       to template. */

    /* Mac - just bunch of data. */
    public function make_mac($data)
    {
        $mac = '';
        foreach ($this->get_service_fields() as $field) {
            if (!array_key_exists($field, $data)) {
                $this->addError("No data to gen mac");
                return false;
            } else {
                $value = $data[$field];
                $mac .= str_pad(strlen($value), 3, '0',STR_PAD_LEFT) . $value;

            }
        }
        return $mac;
    }

    /* Verify mac by bank's public key */
    private function verify_mac($mac_gend, $mac_sent)
    {
        $key_file = file_get_contents($this->config->get("{$this->bank}_key_pub"));
        $key = openssl_pkey_get_public($key_file);
        if (!openssl_verify($mac_gend, base64_decode($mac_sent), $key)) {
            return false;
        }
        return true;
    }

    /* Sign mac by private key */
    private function sign_mac($mac)
    {
        $key_file = file_get_contents($this->config->get("{$this->bank}_key_sec"));
        if (strlen($this->config->get("{$this->bank}_key_sec_pw")) != 0) {
            $key_file_pw = $this->config->get("{$this->bank}_key_sec_pw");
            $key = openssl_pkey_get_private($key_file, $key_file_pw);
        } else {
            $key = openssl_pkey_get_private($key_file);
        }
        if (!openssl_sign($mac, $sig, $key)) {
            $this->addError("OpenSSL refuses to sign mac");
            return false;
        }
        return base64_encode($sig);
    }

    private function hasError()
    {
        return count($this->errors) > 0;
    }

    /* Put errors to log and show nice maintance text to end-user */
    protected  function addError($msg, $cancelled = false)
    {
        $this->language->load('common/pizza');
        if ($cancelled == false) {
            error_log($msg);
            $this->errors["error_title"] = $this->language->get('error_title');
            $this->errors["error_desc"] = $this->language->get('error_desc');
        } else {
            $this->errors["error_title"] = $this->language->get('cancel_title');
            $this->errors["error_desc"] = $this->language->get('cancel_desc');
        }

        $this->errors["error_wait"] = $this->language->get('error_wait');
        $this->errors['continue'] = $this->url->link("checkout/cart", '', 'SSL');
        $this->errors['language'] = $_SESSION["language"];

    }

    private function outputErrors()
    {
        $this->response->setOutput($this->loadView($this->errors, '/template/common/pizza_failture.tpl'));
    }

    /* Triest to convert language from session to iso format language code
       used in bank-s interface. */
    private function get_language()
    {
        switch ((string)$_SESSION["language"]) {
            case "et":
                $lng = "EST";
                break;
            case "ru":
                $lng = "RUS";
                break;
            default:
                return "ENG";
                break;
        }
        return $lng;
    }

    /* Response from bank lands here */
    public function callback()
    {
        $this->language->load('payment/' . $this->bank);
        $fields = array();
        /* Get needed returned data (VK_) to variable */
        foreach ((array)$_REQUEST as $f => $v) {
            if (substr($f, 0, 3) == 'VK_') {
                $fields[$f] = $v;
            }
        }
        /* I need at least service id to continue */
        if (!isset($fields["VK_SERVICE"])) {
            $this->addError("Not enough data recieved");
            $this->outputErrors();
            return;
        }
        $this->service_id = (string)$fields["VK_SERVICE"];
        /* Check if signature is correct */
        $mac_gend = $this->make_mac($fields);
        if ($this->verify_mac($mac_gend, (string)$fields["VK_MAC"]) == false) {
            $this->addError("MAC validation failed");
            $this->outputErrors();
            return;
        }
        /* If bank error or user cancelled */
        if ($this->service_id != 1101) {
            $this->addError("User/bank cancelled!", true);
            $this->outputErrors();
            return;
        }
        /* The following will accept payment */
        $this->load->model('checkout/order');
        $order_info = $this->get_order_info($fields["VK_STAMP"]);
        $order_status_id = $this->config->get("{$this->bank}_order_status_id");
        $this->confirm($order_info["order_id"], $order_status_id);

        $this->response->redirect($this->url->link('checkout/success', '', 'SSL'));
    }

    /**
     * @param $data
     * @param $template
     * @return mixed
     */
    private function loadView($data, $template)
    {
        if (file_exists(DIR_TEMPLATE . $this->config->get('config_template') . $template)) {
            return $this->load->view($this->config->get('config_template') . $template, $data);
        } else {
            return $this->load->view('default' . $template, $data);
        }
    }

    /**
     * @param $data
     * @param $encoding
     * @param string $from_encoding
     * @return mixed
     */
    protected function convertDataToEncoding($data, $encoding, $from_encoding = 'utf-8')
    {
        foreach ($data as $key => $value) {
            $data[$key] = utf8_decode(mb_convert_encoding($value, $encoding, $from_encoding));
        }
        return $data;
    }

    protected function getBankPaymentDetails()
    {
        $order_info = $this->get_order_info();
        $data = array();
        $data["VK_SERVICE"] = $this->service_id;
        $data["VK_VERSION"] = $this->vk_version;
        $data["VK_SND_ID"]  = $this->config->get("{$this->bank}_username");
        $data["VK_STAMP"]   = $order_info['order_id'];
        $data["VK_AMOUNT"]  = $this->currency->format($order_info['total'], "EUR", '', false);
        $data["VK_CURR"]    = "EUR";
        $data["VK_ACC"]     = $this->config->get("{$this->bank}_vk_acc");
        $data["VK_NAME"]    = $this->config->get("{$this->bank}_vk_name");
        $data["VK_REF"]     = "";
        $data["VK_MSG"]     = $this->language->get('text_msg') . ' ' . $order_info['order_id'];
        return $data;
    }


    protected  function confirm($order_id , $status_id ) {
            $this->load->model('checkout/order');
            $this->model_checkout_order->addOrderHistory($order_id , $status_id, '', true);

    }

}